﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Text.RegularExpressions;

namespace Proyecta.Database
{
    /// <summary>
    /// Representa una entidad que permite la interacción
    /// con la base de datos.
    /// </summary>
    public abstract class Helper
    {
        private DbConnection Connection { get; set; }
        protected DbCommand Command { get; set; }
        private DbProviderFactory Factory { get; set; }
        private DbTransaction Transaction { get; set; }

        /// <summary>
        /// Crea un nuevo objeto Helper.
        /// </summary>
        protected Helper()
        {
            // Cargar la configuracion de desde el XML.
            Factory = DbProviderFactories.GetFactory(XMLLoader.Default.Factory);

            // Crear la conexion.
            Connection = Factory.CreateConnection();
            
            // Armar la cadena de conexion.
            StringBuilder strConn = new StringBuilder();
            strConn.Append("Data Source=");
            strConn.Append(XMLLoader.Default.Server);
            strConn.Append(";Initial Catalog=");
            strConn.Append(XMLLoader.Default.DataBase);
           
            if (string.IsNullOrWhiteSpace(XMLLoader.Default.UserName) && string.IsNullOrWhiteSpace(XMLLoader.Default.Password))
            {
                strConn.Append(";Integrated Security=True");
            }
            else
            {
                strConn.Append(";User Id=");
                strConn.Append(XMLLoader.Default.UserName);
                strConn.Append(";Password=");
                strConn.Append(XMLLoader.Default.Password);
            }

            Connection.ConnectionString = strConn.ToString();

            Command = Factory.CreateCommand();
            Command.Connection = Connection;
        }

        /// <summary>
        /// Valida si una cadena de texto contiene los caracteres válidos
        /// para ser utilizado en una sentencia SQL como nombre de un objeto.
        /// </summary>
        /// <param name="name">Nombre a validar.</param>
        /// <returns>Verdadero en caso de ser un nombre válido.</returns>
        protected bool IsValidObjectName(string name)
        {
            return Regex.IsMatch(name, "^[a-zA-Z_]*$");
        }

        /// <summary>
        /// Agrega un campo con su valor al
        /// objeto DbCommand de la clase actual.
        /// </summary>
        /// <param name="campo">Nombre del campo</param>
        /// <param name="valor">Valor del campo</param>
        protected void PrepareParameter(string campo, object valor)
        {
            if (!IsValidObjectName(campo)) throw new Exception("Nombre de campo invalido: " + campo);

            DbParameter param = Factory.CreateParameter();
            param.ParameterName = "@" + campo;

            object dbValue = valor;

            if (valor == null)
            {
                dbValue = DBNull.Value;
            }
            else if (valor is string)
            {
                param.DbType = DbType.String;
            }
            else if (valor is decimal)
            {
                param.DbType = DbType.Decimal;
                if (valor != null && (decimal)valor == decimal.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is float)
            {
                param.DbType = DbType.Single;
                if (valor != null && (float)valor == float.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is byte)
            {
                param.DbType = DbType.Byte;
            }
            else if (valor is Int32)
            {
                param.DbType = DbType.Int32;
                if (valor != null && (Int32)valor == Int32.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is Int64)
            {
                param.DbType = DbType.Int64;
                if (valor != null && (Int64)valor == Int64.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is UInt32)
            {
                param.DbType = DbType.UInt32;
                if (valor != null && (UInt32)valor == UInt32.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is UInt64)
            {
                param.DbType = DbType.UInt64;
                if (valor != null && (UInt64)valor == UInt64.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is DateTime)
            {
                param.DbType = DbType.DateTime;
                if (valor != null && (DateTime)valor == DateTime.MinValue) dbValue = DBNull.Value;
            }
            else if (valor is bool)
            {
                param.DbType = DbType.Boolean;
            }
            /*else
                throw new Exception("Tipo de dato no soportado en la propiedad " + campo + ": " + valor.GetType().FullName);*/

            param.Value = dbValue;
            Command.Parameters.Add(param);
        }

        /// <summary>
        /// Abre la conexion a la base de datos.
        /// </summary>
        /// <param name="startTransaction">Indica si al abrir la conexion, se iniciara una transacción.</param>
        public void Open()
        {
            Connection.Open();
            if (Command.Transaction == null)
                Command.Transaction = Connection.BeginTransaction();
        }

        /// <summary>
        /// Deshace todos los cambios de la transaccion.
        /// </summary>
        protected void Rollback()
        {
            Command.Transaction.Rollback();
        }

        /// <summary>
        /// Cierra la conexión a la base de datos.
        /// </summary>
        public void Close()
        {
            if (Command.Transaction != null) Command.Transaction.Commit();
            Connection.Close();
        }
    }
}